using System;

namespace Sedna.Domain
{
	/// <summary>
	/// read-only value object class that is served to represent weight amounts
	/// in the domain model
	/// 
	/// to map this class to the DB with NHibernate the <see cref="Sedna.Core.Persistence.WeightOzType"/> is used
	/// </summary>
	[Serializable]
	public class Weight: IComparable
	{

		/// <summary>
		/// Troy Oz
		/// </summary>
		public static readonly string OZ = "OZ";

		/// <summary>
		/// Metric kilogram
		/// </summary>
		public static readonly string KG = "KG";


		/// <summary>
		/// represents NULL Weight
		/// </summary>
		private static readonly string NL = "NL";

		/// <summary>
		/// internal factor that is used to store the Weight
		/// </summary>
		public static readonly int Factor = 10000;

		public static readonly Weight Default = new Weight(NL, 0);

        public static readonly Weight ZERO = CreateFromFactoredAmount(OZ, 0);

        public static readonly Weight MinimumNonZero = CreateFromFactoredAmount(1);
	    
	    private string uom;

		//amount in finest units
		private long amount;

		public Weight()
		{
		}

		private Weight(string uom, long amount)
		{
            //if(amount < 0)
            //{
            //    throw new ArgumentException("Weight cannot be negative");
            //}
			this.uom = uom;
			this.amount = amount;
		}

		/// <summary>
		/// short-cut method for creating a Weight in OZ
		/// </summary>
		public static Weight CreateFromFactoredAmount(long amount)
		{
			return CreateFromFactoredAmount(OZ, amount);
		}

        /// <summary>
        ///  short-cut method for creating a Weight in OZ
        /// </summary>
        /// <param name="value">1 value = 1 amount * Factor</param>
        /// <returns></returns>
        public static Weight CreateFromValue(decimal value)
        {
            return CreateFromFactoredAmount(OZ, Convert.ToInt64(value * Factor));
        }


		/// <summary>
		/// creates a Weight in a specified system of measures (e.g. OZ (Troy Ounces) or KG (metric kilograms))
		/// from a value that is specified an amount of finest parts of that system (e.g. in OZ/Factor)
		/// </summary>
		/// <param name="uom">unit of measure (e.f. OZ or KG). so far only OZ is supported. the method throws an exception if something different other that OZ is specified</param>
		/// <param name="amount">amount of UOMs/Factor (e.g. OZ/Factor)</param>
		/// <returns>initialized Weight object</returns>
		public static Weight CreateFromFactoredAmount(string uom, long amount)
		{
			if(OZ != uom)
			{
				throw new ArgumentException(String.Format("Only OZ uom is supported, but was specified {0}", uom));
			}

			return new Weight(uom, amount);
		}


        // Can't have SET accessor, 'cos the class is immutable
		public string Uom
		{
			get { return uom; }
		}

	    // Can't have SET accessor, 'cos the class is immutable
		public long Amount
		{
			get { return amount; }
		}

        // Can't have SET accessor, 'cos the class is immutable
        public decimal Value
        {
            get
            {
                return (decimal)this.amount / (decimal)Factor;
            }
        }
	    
	    public bool IsZero
	    {
	        get { return (amount == 0); }
	    }

	    public bool IsNegative
	    {
            get { return amount < 0 ; }
	    }
	    
		public override bool Equals(object obj)
		{
			if(this == obj)
				return true;

			Weight that = obj as Weight;

			if(that == null)
				return false;

			return this.Uom == that.Uom && this.Amount == that.Amount;
		}


		public override int GetHashCode()
		{
			return (int)this.amount;
		}

		public override string ToString()
		{
            return string.Format("{0} {1}", Value, this.uom);
		}        	    
	    
        public string ToString(string format)
	    {
            decimal value = (decimal)amount / Factor;
            return value.ToString(format);            
	    }


		#region math methods

		public Weight Add(Weight that)
		{
			if(that == null || that.Uom == NL)//NULL + x = x
			{
				return this;
			}

			if(that.Amount == 0 && this.Uom != NL)
			{
				return this;
			}

			if(this.Uom == NL)
			{
				return that;
			}

			if(this.Uom != that.Uom)//we might add auto-conversion logic later
			{
				throw new ArgumentException(String.Format("Unable to perform math operations in different UOMS, convert first; this.Uom={0}, that.Uom={1}", this.Uom, that.Uom));
			}

			return Weight.CreateFromFactoredAmount(this.Uom, this.Amount + that.Amount);
		}

		public Weight Sub(Weight that)
		{
			if(that == null || that.Uom == NL)
			{
				return this;
			}
			if(this.Uom == NL)
			{
				throw new ArithmeticException("Unable to substruct from NULL");
			}
			if(this.Uom != that.Uom)
			{
				throw new ArgumentException(String.Format("Unable to perform math operations in different UOMS, convert first; this.Uom={0}, that.Uom={1}", this.Uom, that.Uom));
			}

			long result = this.Amount - that.Amount;
            //if(result < 0)
            //{
            //    throw new ArithmeticException("Weight cannot be negative.");
            //}

			return Weight.CreateFromFactoredAmount(this.Uom, result);
		}

		#endregion math methods


        #region IComparable Members

        public int CompareTo(object obj)
        {
            Weight objWeight = obj as Weight;
            if (obj == null)
                return 1;
            if (Equals(objWeight))
                return 0;
            return Amount > objWeight.amount ?  1 :  -1;
        }

        #endregion
    }
}
